/*
 * Copyright [2009] [Marcin Rzeźnicki]

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
 */
package mr.go.yaxc;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;

import org.xml.sax.Attributes;

/**
 * This class serves as base class for all actions taken by the configurator.
 * Instances of this class are registered in {@link Actions} class. Because new
 * instance of this class will be created for every node which this action must
 * operate on, you may freely use private fields to pass information between
 * {@link #carryOut(String, Attributes, ScopeHandler)} and
 * {@link #processed(String, ScopeHandler)}
 * 
 * <p>
 * <b>IMPORTANT</b>: If you define your own action and do not use
 * {@link ActionInfo} to describe its creation procedure, you cannot make your
 * subclass private nested class (or anonymuous) because introspection mechanism
 * won't be able to create an instance of this action during parsing
 * </p>
 * 
 * @author Marcin Rzeźnicki
 * 
 */
public abstract class Action {

    /**
     * Helper method which subclasses can use to simplify dynamic object
     * creation - a fairly frequent operation, thus the existence of this
     * method. This method uses {@link Class#newInstance()} internally
     *
     * @param <T>
     *            expected type of created instance
     * @param classname
     *            name of class which instance is to be created
     * @return created instance
     * @throws ClassNotFoundException
     *             when class identified by {@code classname} was not found
     * @throws InstantiationException
     *             when new instance could not be instantiated
     * @throws IllegalAccessException
     *             when class identified by {@code classname} could not be
     *             accessed
     * @throws ClassCastException
     *             when new instance does not conform to type {@code T}
     */
    protected static <T> T createInstance(String classname)
            throws ClassNotFoundException, InstantiationException,
                   IllegalAccessException {
        return (T) Class.forName(classname).newInstance();
    }

    /**
     * Helper method which subclasses can use to simplify dynamic object
     * creation - a fairly frequent operation, thus the existence of this
     * method. This method tries to perform various tricks to choose the right
     * constructor. If in doubt, please refer to {@link TypesHelper}
     *
     * @param <T>
     *            expected type of created instance
     * @param classname
     *            name of class which instance is to be created
     * @param args
     *            constructor arguments
     * @return created instance
     * @throws InstantiationException
     *             when new instance could not be instantiated
     * @throws IllegalAccessException
     *             when class identified by {@code classname} could not be
     *             accessed
     * @throws IllegalArgumentException
     *             when constructor for given {@code args} could not be found
     * @throws ClassNotFoundException
     *             when class identified by {@code classname} was not found
     * @throws InvocationTargetException
     *             when constructor threw an exception
     */
    protected static <T> T createInstance(String classname, Object... args)
            throws InstantiationException, IllegalAccessException,
                   ClassNotFoundException, IllegalArgumentException,
                   InvocationTargetException {
        Class<? extends T> clas = (Class<? extends T>) Class.forName(classname);
        Constructor<T> constructor = TypesHelper.findConstructor(clas,
                                                                 args);
        if (constructor == null) {
            throw new IllegalArgumentException("No suitable constructor");
        }
        return constructor.newInstance(args);
    }
    private ActionInfo actionInfo;
    /**
     * This field is used for passing target of operation down the parsing
     * stack. Child actions can retrieve it using
     * {@link ScopeHandler#parentObject()}
     */
    protected Object object;
    /**
     * This field is used for providing child actions with additional slots for
     * data which should be discovered by parent after children have completed
     * their actions. Most frequently it is used for passing params needed for
     * method call, but implementor can choose to use it for any other purpose.
     * Most default actions pass results of their parsing to their logical
     * parents using this field
     *
     * @see ScopeHandler#addParam(Object)
     * @see ScopeHandler#tryAddParam(Object)
     */
    protected List<Object> params;
    /**
     * This field is similiar to {@link Action#params}. Its purpose is to allow
     * strong coupling of actions which must cooperate during parsing. It allows
     * more bidirectional communication between child actions and parent, all of
     * which put properties they are interested in under agreed name
     *
     * @see ScopeHandler#getParentProperty(String)
     * @see ScopeHandler#setParentProperty(String, Object)
     */
    protected Map<String, Object> properties;

    /**
     * This method is called by parser on entering a node on which action must
     * operate
     *
     * @param name
     *            name of XML node encountered
     * @param attributes
     *            XML attributes
     * @param handler
     *            {@link ScopeHandler}
     * @throws Exception
     *             implementations may throw anything they like. User of the
     *             library will see this exception wrapped in
     *             {@link ConfigurationFileError} which provides info about
     *             document location where processing has been ended
     * @see #processed(String, ScopeHandler)
     */
    public abstract void carryOut(String name, Attributes attributes,
                                  ScopeHandler handler)
            throws Exception;

    /**
     * Implementation should use this method to avoid hardcoding attribute
     * names. This method returns "real" attribute name - a name that user of
     * the library wants. Code idiom which implementators should use for
     * determining attribute value is:
     * <p>
     * <code>
     * attributes.getValue(getAttribute("name"));
     * </code>
     * </p>
     *
     * @param name
     *            attribute name
     * @return attribute name to be used
     */
    protected String getAttribute(String name) {
        if (actionInfo == null) {
            actionInfo = Actions.getActionInfo(this.getClass());
        }
        final Map<String, String> attributesMap = actionInfo.getAttributesMap();
        if (attributesMap == null || !attributesMap.containsKey(name)) {
            final Map<String, String> globalAttributesMap = Actions.
                    getGlobalAttributesMap();
            if (globalAttributesMap == null || !globalAttributesMap.containsKey(
                    name)) {
                return getPrefixedName(name);
            }
            return getPrefixedName(globalAttributesMap.get(name));
        }
        return getPrefixedName(attributesMap.get(name));
    }

    public Object getObject() {
        return object;
    }

    public List<Object> getParams() {
        return params;
    }

    private String getPrefixedName(String name) {
        String prefix = actionInfo.getAttributePrefix();
        if (prefix.isEmpty()) {
            String globalPrefix = Actions.getGlobalAttributePrefix();
            if (globalPrefix.isEmpty()) {
                return name;
            }
            return globalPrefix + ':' + name;
        }
        return prefix + ':' + name;
    }

    public Map<String, Object> getProperties() {
        return properties;
    }

    /**
     * This method is called by parser on leaving a node on which action must
     * operate
     *
     * @param body
     *            accumulated text body of node
     * @param handler
     *            {@link ScopeHandler}
     * @throws Exception
     *             implementations may throw anything they like. User of the
     *             library will see this exception wrapped in
     *             {@link ConfigurationFileError} which provides info about
     *             document location where processing has been ended
     * @see #carryOut(String, Attributes, ScopeHandler)
     */
    public abstract void processed(String body, ScopeHandler handler)
            throws Exception;
}
